<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Common;

final class CommXml
{
    public const XML_VOID                   = 'VOID';
    public const XML_NULL                   = 'NULL';
    public const XML_TRUE                   = 'TRUE';
    public const XML_FALSE                  = 'FALSE';
    public const XML_DEFAULT_ROOT_NAME      = 'xml';
    public const XML_DEFAULT_ENCODING       = 'UTF-8';
    public const XML_DEFAULT_VERSION        = '1.0';
    public const XML_DEFAULT_SPLIT          = '_';

    /**
     * @param string $xmlString
     * @return bool
     * isXmlString
     */
    public static function isXmlString(string $xmlString):bool
    {
        return (strlen($xmlString) > 32 && strtolower(substr($xmlString, 0, 5)) === '<?xml');
    }

    /**
     * @param string $xmlString
     * @param int $options
     * @return array
     * xmlToArray
     */
    public static function xmlToArray(string $xmlString, int $options = -1):array
    {
        !defined('LIBXML_NOCDATA') && define ('LIBXML_NOCDATA', 16384);
        if($options < 0) $options = LIBXML_NOCDATA;
        $xmlObject  = @simplexml_load_string($xmlString, 'SimpleXMLElement', $options);
        return CommArray::objectToArray($xmlObject);
    }

    /**
     * @param array $array
     * @param string $rootName
     * @param string $encoding
     * @param string $version
     * @param string $split
     * @param bool $isEmptyTag
     * @return string
     * arrayToXml
     */
    public static function arrayToXml(array $array, string $rootName = self::XML_DEFAULT_ROOT_NAME, string $encoding = self::XML_DEFAULT_ENCODING, string $version = self::XML_DEFAULT_VERSION, string $split = self::XML_DEFAULT_SPLIT, bool $isEmptyTag = false):string
    {
        strlen($encoding) < 1 && $encoding = self::XML_DEFAULT_ENCODING;
        strlen($version) < 1 && $version = self::XML_DEFAULT_VERSION;
        $xmlString      = '<?xml version="'.$version.'" encoding="'.$encoding.'" ?>';
        if(strlen($rootName) > 0){
            $xmlString .= '<'.$rootName.'>'.self::getArrayXmlString($array, $rootName, $split, $isEmptyTag).'</'.$rootName.'>';
        }else{
            $xmlString .= self::getArrayXmlString($array);
        }
        return $xmlString;
    }

    /**
     * @param array $array
     * @param string $parentName
     * @param string $split
     * @param bool $isEmptyTag
     * @return string
     * getArrayXmlString
     */
    private static function getArrayXmlString(array $array, string $parentName = self::XML_DEFAULT_ROOT_NAME, string $split = self::XML_DEFAULT_SPLIT, bool $isEmptyTag = false):string
    {
        $xmlString          = '';
        if(!CommArray::isEmpty($array)) foreach($array as $key => $val){
            if(is_numeric($key)){
                $key        = $parentName.$split.$key;
            }else{
                if(strpos($key, '/') !== false){
                    $key    = preg_replace_callback("/\s+/", static function(){ return '_'; }, $key);
                    $key    = preg_replace_callback("/\/+/", static function(){ return '_'; }, $key);
                }
            }
            $valType        = strtolower(gettype($val));
            $isTag          = true;
            switch ($valType){
                case CommType::TYPE_ARRAY:{
                    $valString      = self::getArrayXmlString($val, $key, $split, $isEmptyTag);
                    break;
                }
                case CommType::TYPE_OBJECT:{
                    $valString      = self::getArrayXmlString(CommArray::objectToArray($val), $key, $split, $isEmptyTag);
                    break;
                }
                case CommType::TYPE_VOID:{
                    $valString      = self::XML_VOID;
                    break;
                }
                case CommType::TYPE_BOOLEAN:{
                    $valString      = $val ? self::XML_TRUE : self::XML_FALSE;
                    break;
                }
                case CommType::TYPE_NULL:{
                    $valString      = self::XML_NULL;
                    break;
                }
                case CommType::TYPE_DOUBLE:
                case CommType::TYPE_FLOAT:
                case CommType::TYPE_INTEGER:{
                    $valString      = strval($val);
                    break;
                }
                case CommType::TYPE_STRING:{
                    if(is_numeric($val)){
                        $valString  = strval($val);
                    }else{
                        $valString  = strlen($val) < 1 ? '' : (preg_match("/^[a-z\d.\-+ *()?\/|:;,%@=_]+$/i", $val) ? $val : '<![CDATA['.$val.']]>');
                    }
                    break;
                }
                default: {
                    $valString      = '';
                    $isTag          = false;
                }
            }
            if($isTag) $xmlString   .= strlen($valString) < 1 && $isEmptyTag ? '<'.$key.'/>' : '<'.$key.'>'.$valString.'</'.$key.'>';
        }
        return $xmlString;
    }
}